const mongoose=require('mongoose')
const Schema = mongoose.Schema;
const bcrypt=require('bcryptjs')  //bcrypt加严工具需要安装
const SALT_WORK_FACTOR=10;
const MAX_LOGIN_ATTEMPTS = 5   //超过5次就锁定
const LOCK_TIME=2*60*60*1000;  //两个小时

let userSchema = new Schema({
    username: {type:String,required:true, unique:true},
    pwd: {type:String,required:true, unique:true},
    email: {type:String,unique:true},
    LockUntil:Number,   //锁定时间
    loginAttempts:{    //登录次数
        type:Number,
        required:true,   //一定要有
        default:0    //默认
    },
    mate:{
        createAt:{
            type:Date,
            default:Date.now()
        },
        upDateAt:{
            type: Date
        }
    }
});
//给userSchema模型创建一个isLocked方法，方法是get获取返回数据
userSchema.virtual('isLocked').get(function () {
    //这里判断lockUntil是否不为空且大于当前时间，如果是则代表还在锁时间内，
    // 判断原本返回是一个结果，用!!两次取反变为返回一个bool类型
    return !!(this.lockUntil && this.lockUntil > Date.now())
})

userSchema.pre('save',function(next){
    //下句mongo提供的判断某个属性值有没被改变，如果没有(既不是修改操作)则直接执行next()
    if(!this.isModified('pwd'))return next();
//下句是mongo提供的密码加严操作，SALT_WORK_FACTOR是数值，越大越加严，不过服务器消耗资源越大
    bcrypt.genSalt(SALT_WORK_FACTOR,(err,salt)=>{
        if(err)return next(err)   //如果加盐出错则直接执行next
        bcrypt.hash(this.pwd,salt,(error,hash)=>{  //拿到salt再用hash加密
            if(error)return next(error)
            this.password=hash
            next()
        })
    })

    if(this.isNew){    //movieSchema的数据是不是isNew最新的，这个mongoose会验证
        this.mate.createAt=this.mate.upDateAt=Date.now()
    }else{
        this.mate.upDateAt=Date.now()
    }
    next()   //不过数据是不是最新，最后一定要放行到mongoose.model，否则卡住这
})
//给UserSchema对象增加一个函数组，methods是实例化方法(可理解是多函数组成数组使用)
userSchema.methods = {
 //这方法是接收_password客户密码，进行对数据库密码password比对，参数位置顺便不能错。固定代码
    comparePassword: function (_password, pwd) {
        return new Promise((resolve, reject) => {
            bcrypt.compare(_password, pwd, function (err, isMatch) {
                if (!err) resolve(isMatch)
                else reject(err)
            })
        })
    },
    //下面这方法是记录登录次数，和到达后传入锁定的时间
    incLoginAttempts: function (user) {
        const that = this  //先将当前this指向存起来，后面Promise的this会变

        return new Promise((resolve, reject) => {
            //下面判断被锁属性不为空，且当前时间已不在被锁时间内
            if (that.lockUntil && that.lockUntil < Date.now()) {
                that.update({   //更新数据
                    $set: {    //用来添加或者修改属性
                        loginAttempts: 1   //将当前次操作算1次登录
                    },
                    $unset: {   //用来去除属性，这里是去除lockUntil值后设为1毫秒
                        lockUntil: 1   //因为这个不能为0或空，设置锁定时间为1用于后面叠加计数
                    }
                }, function (err) {
                    //更新数据库操作如没有错误，则resolve(true)出去
                    if (!err) resolve(true)
                    else reject(err)
                })
            } else {
                let updates = {  //创建一个更新某数据的对象，
                    $inc: {  //某个数据库属性/数据库字符串加减
                        loginAttempts: 1   //loginAttempts字符串+1，负数就减
                    }
                }
            //如果登录次数超过MAX_LOGIN_ATTEMPTS设定次数，或者用isLocked方法获取到!非 被锁状态
                if (that.loginAttempts + 1 >= MAX_LOGIN_ATTEMPTS && !that.isLocked) {
                    updates.$set = {  //在上面创建的更新数据对象再增加一个lockUntil 字符
                        //判断超过登录次数把2小时加当前时间组成锁定时间
                        lockUntil: Date.now() + LOCK_TIME
                    }
                }

                that.update(updates, err => {  //把上面的更新对象里的数据 更新到数据库
                    if (!err) resolve(true)
                    else reject(err)
                })
            }
        })
    }
}

let Users = mongoose.model('Users', userSchema);
module.exports=Users